#pragma once
#include <exception>
#include <functional>
#include <iostream>
#include <list>
#include <map>
#include <vector>
#include "Includes\SDefine.h"
#include "Tools\Ref.h"

namespace SakuraAge {

class SerializedStream;
class SakuraObject;
template <typename T>
class ObjRef;


/**
 * A class for error when a object reflector is empty but you are using it.
 */
class null_error : public std::runtime_error {
 public:
  std::string objType;
  null_error(std::string type) : runtime_error("Null exception.") {
    objType = type;
  }
  const char* what() const {
    return (std::string("Null object in object ref in type{") + objType + '}')
        .c_str();
  }
};


/**
 * All the class(object class) in the engine are based of this class.
 */
class SakuraObject {
  friend class SerializedStream;
  template <typename>
  friend class ObjRef;

 protected:
  std::list<ObjRef<SakuraObject>*> refs;

 public:
  SakuraObject();
  ~SakuraObject();
  virtual void Serialize(SerializedStream& ss);
};

/**
 * The container of an object(pointer) in engine.
 */
template <typename T>
class ObjRef {
  friend class ObjRef;
  friend class SakuraObject;
  friend class SerializedStream;

 private:
  SakuraObject* obj;

 public:
  ObjRef() { obj = nullptr; }
  ObjRef(SakuraObject* obj) {
    this->obj = obj;
    if (obj != nullptr) this->obj->refs.push_back((ObjRef<SakuraObject>*)this);
  }
  ObjRef operator=(SakuraObject* obj) {
    if (this->obj!=nullptr) this->obj->refs.remove((ObjRef<SakuraObject>*)this);
    this->obj = obj;
    if (obj != nullptr) obj->refs.push_back((ObjRef<SakuraObject>*)this);
    return *this;
  }
  template <typename T1>
  ObjRef operator=(ObjRef<T1> ref) {
    if (!obj) obj->refs.remove((ObjRef<SakuraObject>*)this);
    if constexpr (is_base_of<T, T1>::value) {
      obj = ref.obj;
    } else {
      obj = dynamic_cast<T>(ref.obj);
    }
    if (obj!=nullptr) obj->refs.push_back((ObjRef<SakuraObject>*)this);
  }

  bool operator==(typename void* a) const { return obj == a; }

  template <typename T>
  bool operator==(ObjRef<T> a) const {
    return a.obj == obj;
  }

  bool operator!=(typename void* a) const { return obj != a; }

  template <typename T>
  bool operator!=(ObjRef<T> a) const {
    return a.obj != obj;
  }

  T* operator->() const {
    if (!obj) throw new null_error(typeid(T).name());
    return (T*)obj;
  }
  T& operator*() const {
    if (!obj) throw new null_error(typeid(T).name());
    return *(T*)obj;
  }
};

using ObjectRef = SakuraObject*;

class Component;
class ItemFactor;
class Transform;

/**
 * All the object in game are item.
 * Use component in object to fill with the game.
 */
class Item : public SakuraObject, public RefBase<Item> {
  friend class ItemFactor;
  friend class Component;
  friend class Cardinal;
  friend class Item;
  friend class Scene;
  friend class RefBase<Item>;

  Item();
  ~Item();

 public:
  void forEach(std::function<void(Item*)>, bool = false);
  bool forEach(std::function<bool(Item*)>, bool = false);
  /**
   * Find a component in an item.
   */
  template <typename T>
  ObjRef<T> GetComponent() {
    static_assert(!std::is_base_of<Component, T>::value);
    for (ObjRef<Component> comp : components) {
      if (typeid(*comp) == typeid(T)) {
        return comp;
      }
    }
    return ObjRef<T>(nullptr);
  }
  /**
   * Find components in an item.
   */
  template <typename T>
  std::vector<ObjRef<T>> GetComponents() {
    static_assert(!std::is_base_of<Component, T>::value);
    std::vector<T*> result;
    for (ObjRef<Component> comp : components) {
      if (typeid(*comp) == typeid(T)) {
        result += comp;
      }
    }
    return result;
  }
  /**
   * Find component in the nearest father.
   */
  template <typename T>
  ObjRef<T> GetComponentInFather() {
    Item* i = this->transform->father;
    while (i != nullptr&& i->GetComponent<T>() == nullptr) {
      i = i->transform->father;
    }
    if (i != nullptr) {
      return i->GetComponent<T>();
    }
    return ObjRef<T>(nullptr);
  }

 private:
  bool isActive;
  std::vector<ObjRef<Component>> components;

 public:
  PROPERTY(bool, active);
  GET(active) { return isActive; }
  SET(active);
  ObjRef<Transform> transform;
  std::string name;
  virtual void Serialize(SerializedStream& ss);
};
class ItemFactor {
  friend class Cardinal;
  static std::vector<ObjRef<Item>> list;
  friend class Scene;

 public:
  static Item* CreateItem();
  static Item* CreateItem(const std::string& name);
};
class Component : public SakuraObject, public RefBase<Component> {
  friend class Item;

 private:
  static std::vector<ObjRef<Component>> components;

 public:
  ~Component();
  Component();

 public:
  virtual void OnDestory();
  virtual void Awake();
  virtual void Update();
  virtual void OnEnable();
  virtual void OnDisable();

  virtual void Serialize(SerializedStream& ss);

  bool Active;

  ObjRef<Item> item;

  /**
   * Add a component into a item.
   */
  template <class T>
  static T* Add(ObjRef<Item> i) {
    T* p = new T();
    i->components.push_back((Component*)p);
    ((Component*)p)->Awake();
    (((Component*)p)->item) = i;
    /*
    std::string typeidName = typeid(T).name(), type;
    std::string::size_type pos = s.find("Needed<");
    while (pos != typeidName.npos) {
      for (int i = pos + 7; typeidName[i] != '>'; i++) {
        type += typeidName[i];
      }
    }*/
    return p;
  }
};
}  // namespace SakuraAge
